package top.archiesean.common.security.properties;

import cn.hutool.core.util.ReUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import top.archiesean.common.core.utils.context.SpringContextUtil;
import top.archiesean.common.security.annotation.AnonymousAccess;

import java.util.*;
import java.util.regex.Pattern;

/**
 * @author ArchieSean
 * @description 资源服务器对外直接暴露URL，如果设置了context-path ，需要特殊处理
 * @date 2024-01-09 08:49
 */
@Slf4j
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean {
    /**
     * 用于校验是否是以路径拼参的形式的接口路径
     */
    private static final Pattern PATTERN = Pattern.compile("\\{.*?}");

    /**
     * 用于替换{a}形式的参数
     */
    private static final String ASTERISK = "*";

    /**
     * 默认不鉴权的路径
     */
    private static final String[] DEFAULT_IGNORE_URLS = new String[]{"/actuator/**","/druid/**"};

    /**
     * 需要在配置文件中配置的接口路径
     */
    @Setter
    @Getter
    private Set<String> urls = new HashSet<>();


    /**
     * 初始化bean时，默认调用，作用： 用于获取所有被自定义注解@AnonymousAccess标记的handler的请求路径
     */
    @Override
    public void afterPropertiesSet() {
        urls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));

        RequestMappingHandlerMapping mappingHandlerMapping = (RequestMappingHandlerMapping) SpringContextUtil.getBean("requestMappingHandlerMapping");

        //获取所有的handler
        Map<RequestMappingInfo, HandlerMethod> map = mappingHandlerMapping.getHandlerMethods();

        //遍历处理所有接口当中，类以及方法上的自定义注解
        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);
            //先获取方法上的注解
            AnonymousAccess method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), AnonymousAccess.class);
            Optional.ofNullable(method).ifPresent(anonymousAccess -> info.getPathPatternsCondition().getPatterns()
                    .forEach(url -> this.urls.add(ReUtil.replaceAll(url.toString(), PATTERN, ASTERISK))));

            //获取类上的注解
            AnonymousAccess clazz = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), AnonymousAccess.class);
            Optional.ofNullable(clazz).ifPresent(consumer -> Objects.requireNonNull(info.getPathPatternsCondition().getPatterns())
                    .forEach(url -> this.urls.add(ReUtil.replaceAll(url.toString(), PATTERN, ASTERISK))));
        });

    }
}
